import {
  computed,
  effectScope,
  getCurrentInstance,
  inject,
  isRef,
  reactive,
  toRefs,
} from "vue";
import { piniaSymbol } from "./rootStore";

function isObject(obj) {
  return typeof obj === "object" && obj !== null;
}

function createOptionStore(id, options, pinia) {
  const { state, getters = {}, actions = {} } = options;
  function setup() {
    pinia.state.value[id] = state ? state() : {};
    const localState = toRefs(pinia.state.value[id]);
    return Object.assign(
      localState,
      actions,
      Object.keys(getters).reduce((gettersObj, getterName) => {
        gettersObj[getterName] = computed(() => {
          const store = pinia._s.get(id);
          return getters[getterName].call(store);
        });
        return gettersObj;
      }, {})
    );
  }
  const store = createSetupStore(id, setup, pinia);
  store.$reset = function () {
    const newState = state ? state() : {};
    store.$patch(newState);
  };
}

function merge(target, partialState) {
  for (const key in partialState) {
    const newState = partialState[key];
    const oldState = target[key];
    if (isObject(newState) && isObject(oldState) && !isRef(newState)) {
      target[key] = merge(oldState, newState);
    } else {
      target[key] = newState;
    }
  }
  return target;
}

function createSetupStore(id, setup, pinia) {
  const store = reactive({
    $patch(partialState) {
      merge(pinia.state.value[id], partialState);
    },
  });
  let scope;
  function wrapAction(action) {
    return function () {
      let result = action.call(this, ...arguments);
      return result;
    };
  }
  // 父scope -》 子scope
  const setupStore = pinia._e.run(() => {
    // 划分父子作用域
    scope = effectScope();
    return scope.run(() => {
      return setup();
    });
  });
  for (const key in setupStore) {
    if (typeof setupStore[key] === "function") {
      setupStore[key] = wrapAction(setupStore[key]);
    }
  }
  Object.assign(store, setupStore);
  pinia._s.set(id, store);
  return store;
}

export function defineStore(idOptions, setup) {
  let id;
  let options;

  const isSetupStore = typeof setup === "function";
  if (typeof idOptions === "string") {
    id = idOptions;
    options = setup;
  } else {
    id = idOptions.id;
    options = idOptions;
  }

  function useStore() {
    const instance = getCurrentInstance();
    const pinia = instance && inject(piniaSymbol);

    if (!pinia._s.has(id)) {
      if (isSetupStore) {
        createSetupStore(id, setup, pinia);
      } else {
        createOptionStore(id, options, pinia);
      }
    }
    const store = pinia._s.get(id);
    return store;
  }
  return useStore;
}
